/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief Multicast entry storage implementation file.
 * \author Antti Maula <antti.maula@tkk.fi>
 */
//*****************************************************************************
//*****************************************************************************
#include "multicasttable.h"
#include "owndebug.h"
#include "ownutils.h"
#include "idmap.h"
#include <inttypes.h>
#include <string.h>
#include <assert.h>

//*****************************************************************************
//*****************************************************************************
typedef struct SMulticastTable
{
  unsigned int multicastIdHashes;
  unsigned int multicastSubscriberHashes;

  TIDMap entries;
  uint32_t lastMulticastId;

} TMulticastTable;
//*****************************************************************************
static TMulticastTable multicasttable;
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

static void multicasttableSubscriberEntryInit(TMulticastSubscriberEntry *entry,
                                              uint32_t subscriber_id)
{
  memset(entry, 0x00, sizeof(TMulticastSubscriberEntry));
  time(&entry->last_update_s);
  entry->subscriber_id = subscriber_id;
  return;
}
//*****************************************************************************

static void multicasttableSubscriberEntryDeinit(TMulticastSubscriberEntry *entry)
{
  return;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

static void multicasttableEntryInit(TMulticastTableEntry *entry, 
                                    const int maxSubscribers)
{
  memset(entry, 0x00, sizeof(TMulticastTableEntry));
  idMapInit(&entry->subscribers, maxSubscribers);
}
//*****************************************************************************

static void multicasttableEntryDeinit(TMulticastTableEntry *entry)
{
  TIDMapEntryIterator iter;
  TIDMapEntry *identry;
  idMapGetNextEntryIterator(&entry->subscribers, &iter);
  while((identry=(TIDMapEntry *)idMapGetNextEntry(&entry->subscribers, &iter))) {
    TMulticastSubscriberEntry *sentry = (TMulticastSubscriberEntry *)identry->entry;
    multicasttableSubscriberEntryDeinit(sentry);
    free(sentry);
  }
  idMapDeinit(&entry->subscribers);
}
//*****************************************************************************

int multicasttableEntryAddSubscriber(TMulticastTableEntry *entry, 
                                     uint32_t subscriber_id)
{
  int result = -1;
  TMulticastSubscriberEntry *newsub = (TMulticastSubscriberEntry *)malloc(sizeof(TMulticastSubscriberEntry));
  assert(newsub != NULL);

  // Init entry with data
  multicasttableSubscriberEntryInit(newsub, subscriber_id);

  // Attempt to add to idMap backend, check result and delete reserved if fails.
  result = idMapAddEntry(&entry->subscribers, subscriber_id, (void*)newsub);
  if (result) {
    multicasttableSubscriberEntryDeinit(newsub);
    free(newsub);
  }

  return result;
}
//*****************************************************************************

const TMulticastSubscriberEntry *multicasttableEntryFindSubscriber(const TMulticastTableEntry *entry, 
                                                                   uint32_t subscriber_id)
{
  const TIDMapEntry *idmapentry = idMapGetEntry(&entry->subscribers, subscriber_id);
  if (idmapentry) {
    return (const TMulticastSubscriberEntry *)idmapentry->entry;
  } else {
    return NULL;
  }
}
//*****************************************************************************

int multicasttableEntryDeleteSubscriber(TMulticastTableEntry *entry, 
                                        uint32_t subscriber_id)
{
  int result = -1;
  const TIDMapEntry *idmapentry = idMapGetEntry(&entry->subscribers, subscriber_id);
  if (idmapentry) {
    multicasttableSubscriberEntryDeinit((TMulticastSubscriberEntry*)idmapentry->entry);
    free(idmapentry->entry);
    idMapDeleteEntry(&entry->subscribers, subscriber_id);
    result = 0;
  }
  return result;
}
//*****************************************************************************

void multicasttableEntryGetSubscriberIterator(const TMulticastTableEntry *entry, 
                                              TMulticastSubscriberIterator *iter)
{
  idMapGetNextEntryIterator(&entry->subscribers, iter);
}
//*****************************************************************************

TMulticastSubscriberEntry *multicasttableEntryGetNextMulticastSubscriberEntry(const TMulticastTableEntry *entry, 
                                                                              TMulticastSubscriberIterator *iter)
{
  TMulticastSubscriberEntry *result = NULL;
  const TIDMapEntry *idmapentry = idMapGetNextEntry(&entry->subscribers, iter);
  if (idmapentry) {
    result = (TMulticastSubscriberEntry *)idmapentry->entry;
  }
  
  return result;
}
//*****************************************************************************

long unsigned int multicasttableEntryGetSubscriberCount(const TMulticastTableEntry *entry)
{
  return idMapGetTotalElementCount(&entry->subscribers);
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

void multicasttableInit(const int maxMulticastIds, const int maxSubscribers)
{
  // Reset first to satisfy valgrind, and prevent information leaks
  memset(&multicasttable, 0x00, sizeof(TMulticastTable));

  // Store limits
  multicasttable.multicastIdHashes = maxMulticastIds >> 3;
  multicasttable.multicastSubscriberHashes = maxSubscribers >> 3;
  multicasttable.lastMulticastId = 0; // Explicitly set even though memset done.
  
  // Init initial containers
  idMapInit(&multicasttable.entries, multicasttable.multicastIdHashes);
  
  return;
}
//*****************************************************************************

void multicasttableDeinit(void)
{
  // Clean up all containers
  TIDMapEntryIterator iter;
  TIDMapEntry *entry;
  idMapGetNextEntryIterator(&multicasttable.entries, &iter);
  while((entry=(TIDMapEntry *)idMapGetNextEntry(&multicasttable.entries, &iter))) {
    TMulticastTableEntry *mcentry = (TMulticastTableEntry *)entry->entry;
    multicasttableEntryDeinit(mcentry);
    free(mcentry);
  }
  idMapDeinit(&multicasttable.entries);
  memset(&multicasttable, 0x00, sizeof(TMulticastTable));
  return;
}
//*****************************************************************************

uint32_t multicasttableGetUniqueMulticastId(void)
{
  return ++multicasttable.lastMulticastId;
}
//*****************************************************************************

int multicasttableCreateEntry(const uint32_t multicast_id, TMulticastTableEntry **newentryptr)
{
  int result = 0;
  
  // Construct new entry
  TMulticastTableEntry *newentry = (TMulticastTableEntry *)malloc(sizeof(TMulticastTableEntry));
  multicasttableEntryInit(newentry, multicasttable.multicastSubscriberHashes);
  
  // Assign already known info
  newentry->multicast_id = multicast_id;
  
  // Check that this conforms to unique numbering
  if (multicasttable.lastMulticastId < multicast_id) {
    dPrint(ODTEST,"Note: LastMulticastID was updated from '%08x' to '%08x' - Probably due to local ID reassignments",
           multicasttable.lastMulticastId, multicast_id);
    multicasttable.lastMulticastId = multicast_id;
  }

  // Now add!
  result = idMapAddEntry(&multicasttable.entries, multicast_id, (void*)newentry);
  if (result) {
    multicasttableEntryDeinit(newentry);
    free(newentry);

  } else if (newentryptr) {
    *newentryptr = newentry;

  }

  return result;
}
//*****************************************************************************

TMulticastTableEntry *multicasttableFindEntry(const uint32_t multicast_id)
{
  TMulticastTableEntry *entry = NULL;

  const TIDMapEntry *idmentry = idMapGetEntry(&multicasttable.entries, multicast_id);
  if (idmentry) {
    // Entry was found!
    entry = (TMulticastTableEntry *)idmentry->entry;
  }

  return entry;
}
//*****************************************************************************

int multicasttableDeleteEntry(const uint32_t multicast_id)
{
  int result = -1;
  const TIDMapEntry *idmentry = idMapGetEntry(&multicasttable.entries, multicast_id);
  if (idmentry) {
    // Entry was found!
    TMulticastTableEntry *entry = (TMulticastTableEntry *)idmentry->entry;
    multicasttableEntryDeinit(entry);
    free(entry);
    idMapDeleteEntry(&multicasttable.entries, multicast_id);
  }

  return result;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
#ifdef COMPILE_UNIT_TEST
/**
gcc -O0 -g3 -Wall -I. -I../../ -I../../protocol -I../../../utils -I../../clientlib -DLINUX_OS -DUSE_SELECT_IO -DCOMPILE_UNIT_TEST multicasttable.c ../../../utils/own*.c -lpthread -lrt ../../idmap.c ../../strmap.c -o multicasttable.unit
*/
#include <stdio.h>
int main(void)
{
  long int i,j;
  ownTime_ms_t begin;
  ownTime_ms_delta_t took;
  int r;

  debugInit();
  debugSetGlobalDebugLvl(ODINFO);

  const int maxes[2] = { 1000, 1000 };
  multicasttableInit(maxes[0], maxes[1]);

  
  int limit = 1000;
  TMulticastTableEntry *entry = NULL;

  // Insert Entries
  begin = ownTime_get_ms();
  for(i=0; i < limit; ++i) {
    uint32_t mcid = multicasttableGetUniqueMulticastId();
    assert(mcid > 0);
    //    printf("Creating multicastentry with id %u\n", mcid);
    r = multicasttableCreateEntry(mcid, &entry);
    assert(r == 0);
    //    assert(multicasttableFindEntry(mcid) == entry);
  }
  took = ownTime_get_ms_since(begin);
  printf("%ld multicasttableCreateEntry:s completed, operation took %lu ms, %.2f us per entry\n", 
         i, (unsigned long)took, (1000.0*took)/i);
  

  // Find & insert subs (We know that "Uniq" sequencing starts from 1 and linearly up, so exploit that while testing);
  for(i=1; i < limit; ++i) {
    entry = multicasttableFindEntry(i);
    assert(entry != NULL);
    
    // Insert subscribers
    begin = ownTime_get_ms();
    for(j=0; j < 10*limit; ++j) {
      multicasttableEntryAddSubscriber(entry, j);
    }
    took = ownTime_get_ms_since(begin);
    printf("%ld multicasttableEntryAddSubscriber():s completed, operation took %lu ms, %.2f us per entry\n", 
           j, (unsigned long)took, (1000.0*took)/j);
  }

  // Iterate through all subs
  begin = ownTime_get_ms();
  for(i=1; i < limit; ++i) {
    entry = multicasttableFindEntry(i);
    assert(entry != NULL);
    TMulticastSubscriberIterator msi;
    multicasttableEntryGetSubscriberIterator(entry, &msi);

    // Iterate subs.
    TMulticastSubscriberEntry *mse = NULL;
    unsigned long count = 0;
    
    // Do nothing while iterating :)
    while((mse=multicasttableEntryGetNextMulticastSubscriberEntry(entry, &msi))) ++count;
    assert(count == limit*10);
    
  }
  took = ownTime_get_ms_since(begin);
  printf("Accessing all %lu Subscribers in %lu multicast_ids took %lu ms, %.2f us per entry (%lu total accessed Subs)\n", 
         (unsigned long)(limit), 
         (unsigned long)(limit), 
         (unsigned long)took, 
         (1000.0*took)/(limit*limit),
         (unsigned long)(limit*limit));
  

  multicasttableDeinit();

  return 0;
}
#endif
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
